
/**
 * Java运算符：7种算数运算符，赋值运算符，7种位运算符，6种比较运算符，6种逻辑运算符和三目运算符
 * 编译：javac -encoding UTF-8 Operator.java
 * 运行：java Operator
 */
public class Operator {
    /**
     * 算术运算符
     * 7个基本的算术运算符：
     * + 加
     * - 减
     * * 乘
     * / 除
     * % 求余
     * ++ 自加
     * -- 自减
     * - 负
     */
    public static void ArithmeticOperators() {
        double a = 5.2;
        double b = 3.1;
        double sum = a + b;
        System.out.println(sum);    // 8.3
        double sub = a - b;
        System.out.println(sub);    // 2.1
        double mutiply = a * b;
        System.out.println(mutiply);    // 16.12

        /*
         * 除法运算符有些特殊，如果除法运算符的两个操作数都是整数类型，则计算结果也是整数，
         * 就是将自然除法的结果截断取整，例如19/4的结果是4，而不是5。
         * 如果除法运算符的两个操作数都是整数类型，则除数不可以是0，否则将引发除以零异常。
         *
         * 但如果除法运算符的两个操作数有一个是浮点数，或者两个都是浮点数，则计算结果也是浮点数，这个结果就是自然除法的结果。
         * 而且此时允许除数是0，或者0.0，得到结果是正无穷大或负无穷大。
         */
        double div = a / b;
        System.out.println(div);    // 1.6774193548387097
        System.out.println("5除以0.0的结果是：" + 5 / 0.0);    // 5除以0.0的结果是：Infinity
        System.out.println("-5除以0.0的结果是：" + -5 / 0.0);  // -5除以0.0的结果是：-Infinity
        // 下面的代码将出现异常
        // java.lang.ArithmeticException: / by zero
//        System.out.println("-5除以0的结果是：" + -5 / 0);

        /*
         * 求余运算的结果不一定总是整数，它的计算结果是使用第一个操作数除以第二个操作数，得到一个整除的结果后剩下的值就是余数。
         * 由于求余运算也需要进行除法运算，因此如果求余运算的两个操作数都是整数类型，则求余运算的第二个操作数不能是0，
         * 否则将引发除以零异常。
         * 如果求余运算的两个操作数中有一个或者两个都是浮点数，则允许第二个操作数是0或0.0，只是求余运算的结果是非数：NaN。
         * 0或0.0对零以外的任何数求余都将得到0或0.0。
         */
        double mod = a % b;
        System.out.println(mod);    // 2.1
        System.out.println("5对0.0求余的结果是：" + 5 % 0.0);   // 5对0.0求余的结果是：NaN
        System.out.println("-5.0对0求余的结果是：" + -5.0 % 0); // -5.0对0求余的结果是：NaN
        System.out.println("0对5.0求余的结果是：" + 0 % 5.0);   // 0对5.0求余的结果是：0.0
        System.out.println("0对0.0求余的结果是：" + 0 % 0.0);   // 0对0.0求余的结果是：NaN
        // 下面的代码将出现异常
        // java.lang.ArithmeticException: / by zero
//        System.out.println("-5对0求余的结果是：" + -5 % 0);

        int a1 = 5;
        int b1 = a1++ + 6;
        System.out.println(a1 + "\t" + b1); // 6	11
        a1 = 5;
        b1 = ++a1 + 6;
        System.out.println(a1 + "\t" + b1); // 6	12
        a1 = 5;
        b1 = --a1 + 6;
        System.out.println(a1 + "\t" + b1); // 6	10

        a1 = -5;
        a1 = -a1;
        System.out.println(a1); // 5

        /*
         * Java并没有提供其他更复杂的运算符，如果需要完成乘方、开方等运算，
         * 则可借助于java.lang.Math类的工具方法完成复杂的数学运算，
         */
        b = Math.pow(a, 5); // a的5次方
        System.out.println(b);
        double c = Math.sqrt(a); // a的平方根
        System.out.println(c);
        double d = Math.random();    // 返回一个[0, 1)之间的伪随机数
        System.out.println(d);
        double e = Math.sin(1.57);  // 求1.57的sin函数值
        System.out.println(e);
    }

    /**
     * 赋值运算符
     * 赋值运算符用于为变量指定变量值，与C类似，Java也使用=作为赋值运算符。
     */
    public static void AssignOperations() {
        // 使用赋值运算符将一个直接量值赋给变量。
        String str = "Java";
        double pi = 3.14;
        boolean visited = true;

        // 也可使用赋值运算符将一个变量的值赋给另一个变量。
        String str2 = str;

        // 值得指出的是，赋值表达式是有值的，赋值表达式的值就是右边被赋的值。
        // 例如String str2=str表达式的值就是str。因此，赋值运算符支持连续赋值，
        // 通过使用多个赋值运算符，可以一次为多个变量赋值。
        int a, b, c;
        a = b = c = 7;
        // 虽然Java支持这种一次为多个变量赋值的写法，但这种写法导致程序的可读性降低，因此不推荐这样写。
        System.out.println(a + "\t" + b + "\t" + c);    // 7	7	7

        // 赋值运算符还可用于将表达式的值赋给变量。
        double d1 = 12.34;
        double d2 = d1 + 5;
        System.out.println(d2); // 17.34
    }

    /**
     * 位运算符
     * ➢ &：按位与。当两位同时为1时才返回1。
     * ➢ |：按位或。只要有一位为1即可返回1。
     * ➢ ～：按位非。单目运算符，将操作数的每个位（包括符号位）全部取反。
     * ➢ ^：按位异或。当两位相同时返回0，不同时返回1。
     * ➢ <<：左移运算符。
     * ➢ >>：右移运算符。
     * ➢ >>>：无符号右移运算符。
     * 一般来说，位运算符只能操作整数类型的变量或值。
     * 按位非只需要一个操作数，这个运算符将把操作数在计算机底层的二进制码按位（包括符号位）取反。
     */
    public static void BitOperations() {
        // 按位与、按位或、按位异或
        System.out.println(5 & 9);  // 1
        System.out.println(5 | 9);  // 13
        System.out.println(5 ^ 9);  // 12
        // 5的二进制码是00000101（省略了前面的24个0），而9的二进制码是00001001（省略了前面的24个0）。
        // 运算过程如下
        /*
         *  00000101     00000101     00000101
         * &00001001    |00001001    ^00001001
         * ---------    ---------    ---------
         *  00000001     00001101     00001100
         */

        // 按位取反
        System.out.println(~-5);    // 4
        /*
         * -5的原码  10000000000000000000000000000101
         *                     ↓ 原码除符号位外取反得到反码
         * -5的反码  11111111111111111111111111111010
         *                     ↓ 反码加1得到补码
         * -5的补码  11111111111111111111111111111011   负数在计算机里是以补码存在，这就是计算机里存的-5
         *                     ↓ 取反后最高位（符号位为0），成为正数
         *           00000000000000000000000000000100   正数的补码和原码相同，这就是4
         */

        // 左移运算符是将操作数的二进制码整体左移指定位数，左移后右边空出来的位以0填充。
        System.out.println(5 << 2);    // 20
        System.out.println(-5 << 2);   // -20
        // 下面以-5为例来介绍左移运算的运算过程
        //                  |11111111111111111111111111111011
        //                11|11111111111111111111111111101100
        // 移出来的被截断 ↑                                 ↑ 后面补充2个0

        /*
         * Java的右移运算符有两个：>>和>>>，对于>>运算符而言，把第一个操作数的二进制码右移指定位数后，
         * 左边空出来的位以原来的符号位填充，即如果第一个操作数原来是正数，则左边补0；如果第一个操作数是负数，则左边补1。
         * >>>是无符号右移运算符，它把第一个操作数的二进制码右移指定位数后，左边空出来的位总是以0填充。
         */
        System.out.println(-5 >> 2);    // -2
        System.out.println(-5 >>> 2);   // 1073741822

        /*
         * 进行移位运算时还要遵循如下规则。
         * ➢ 对于低于int类型（如byte、short和char）的操作数总是先自动类型转换为int类型后再移位。
         * ➢ 对于int类型的整数移位a>>b，当b>32时，系统先用b对32求余（因为int类型只有32位），得到的结果才是真正移位的位数。
         * 例如，a>>33和a>>1的结果完全一样，而a>>32的结果和a相同。
         * ➢ 对于long类型的整数移位a>>b，当b>64时，总是先用b对64求余（因为long类型是64位），得到的结果才是真正移位的位数。
         *
         * 注意：当进行移位运算时，只要被移位的二进制码没有发生有效位的数字丢失（对于正数而言，通常指被移出的位全部都是0），
         * 不难发现左移n位就相当于乘以2的n次方，右移n位则是除以2的n次方。
         * 不仅如此，进行移位运算不会改变操作数本身，只是得到了一个新的运算结果，而原来的操作数本身是不会改变的。
         */
    }

    /**
     * 扩展后的赋值运算符
     * 赋值运算符可与算术运算符、位移运算符结合，扩展成功能更加强大的运算符。扩展后的赋值运算符如下。
     * ➢ -=：对于x-=y，即对应于x=x-y。
     * ➢ *=：对于x*=y，即对应于x=x*y。
     * ➢ /=：对于x/=y，即对应于x=x/y。
     * ➢ %=：对于x%=y，即对应于x=x%y。
     * ➢ &=：对于x&=y，即对应于x=x&y。
     * ➢ |=：对于x|=y，即对应于x=x|y。
     * ➢ ^=：对于x^=y，即对应于x=x^y。
     * ➢ <<=：对于x<<=y，即对应于x=x<<y。
     * ➢ >>=：对于x>>=y，即对应于x=x>>y。
     * ➢ >>>=：对于x>>>=y，即对应于x=x>>>y。
     * 只要能使用这种扩展后的赋值运算符，通常都推荐使用它们。
     * 因为这种运算符不仅具有更好的性能，而且程序会更加健壮。
     */
    public static void EnhanceAssignOperations() {
        // 下面程序示范了+=运算符的用法。
        byte a = 5;
        // 下面语句出错，因为5默认是int类型，a+5就是int类型，
        // 把int类型赋给byte类型的变量，所以出错。
//        a = a + 5;
        a += 5;
    }

    /**
     * 比较运算符
     * 比较运算符用于判断两个变量或常量的大小，比较运算的结果是一个布尔值（true或false）。
     * Java支持的比较运算符如下。
     * ➢ >：大于，只支持左右两边操作数是数值类型。如果前面变量的值大于后面变量的值，则返回true。
     * ➢ >=：大于等于，只支持左右两边操作数是数值类型。如果前面变量的值大于等于后面变量的值，则返回true。
     * ➢ <：小于，只支持左右两边操作数是数值类型。如果前面变量的值小于后面变量的值，则返回true。
     * ➢ <=：小于等于，只支持左右两边操作数是数值类型。如果前面变量的值小于等于后面变量的值，则返回true。
     * ➢ ==：等于，如果进行比较的两个操作数都是数值类型，即使它们的数据类型不相同，只要它们的值相等，也都将返回true。
     * 例如97=='a'返回true，5.0==5也返回true。如果两个操作数都是引用类型，那么只有当两个引用变量的类型具有父子关系时才可以比较，
     * 而且这两个引用必须指向同一个对象才会返回true。Java也支持两个boolean类型的值进行比较，例如，true==false将返回false。
     * <p>
     * 注意：基本类型的变量、值不能和引用类型的变量、值使用==进行比较；boolean类型的变量、值不能与其他任意类型的变量、值使用==进行比较；
     * 如果两个引用类型之间没有父子继承关系，那么它们的变量也不能使用==进行比较。
     * <p>
     * ➢ !=：不等于，如果进行比较的两个操作数都是数值类型，无论它们的数据类型是否相同，只要它们的值不相等，也都将返回true。
     * 如果两个操作数都是引用类型，只有当两个引用变量的类型具有父子关系时才可以比较，只要两个引用指向的不是同一个对象就会返回true。
     */
    public static void ComparableOperators() {
        System.out.println("5是否大于4.0：" + (5 > 4.0));    // true
        System.out.println("5和5.0是否相等：" + (5 == 5.0));  // true
        System.out.println("97和'a'是否相等：" + (97 == 'a'));    // true
        System.out.println("true和false是否相等：" + (true == false));    // false

        // 比较引用类型
        Operator t1 = new Operator();
        Operator t2 = new Operator();
        System.out.println("t1是否等于t2: " + (t1 == t2));   // false
        Operator t3 = t1;
        System.out.println("t1是否等于t2: " + (t1 == t3));   // true
    }

    /**
     * 逻辑运算符
     * 逻辑运算符用于操作两个布尔型的变量或常量。逻辑运算符主要有如下6个。
     * ➢ &&：与，前后两个操作数必须都是true才返回true，否则返回false。
     * ➢ &：不短路与，作用与&&相同，但不会短路。
     * ➢ ||：或，只要两个操作数中有一个是true，就可以返回true，否则返回false。
     * ➢ |：不短路或，作用与||相同，但不会短路。
     * ➢ ！：非，只需要一个操作数，如果操作数为true，则返回false；如果操作数为false，则返回true。
     * ➢ ^：异或，当两个操作数不同时才返回true，如果两个操作数相同则返回false。
     */
    public static void LogicOperators() {
        System.out.println(!false);
        System.out.println(5 > 3 && '6' > 10);
        System.out.println(4 >= 5 || 'c' > 'a');
        System.out.println(4 >= 5 ^ 'c' > 'a');

        // | 与 ||的区别
        int a = 5;
        int b = 10;
        // | 左右 两边的表达式都会执行
        if (a > 4 | b++ > 10) {
            // a=5,b=11
            System.out.println("a=" + a + ",b=" + b);
        }
        int c = 5;
        int d = 10;
        // || 如果左边满足条件，则右边不会执行
        if (c > 4 || d++ > 10) {
            // c=5,d=10
            System.out.println("c=" + c + ",d=" + d);
        }
    }

    /**
     * 三目运算符
     * 三目运算符的规则是：先对逻辑表达式expression求值，
     * 如果逻辑表达式返回true，则返回第二个操作数的值，
     * 如果逻辑表达式返回false，则返回第三个操作数的值。
     */
    public static void ThreeOperators() {
        String str = 5 > 3 ? "5大于3" : "5不大于3";
        System.out.println(str);    // 5大于3

        // 三目运算符可以嵌套，嵌套后的三目运算符可以处理更复杂的情况，
        int a = 11;
        int b = 12;
        System.out.println(a > b ? "a大于b" : (a < b ? "a小于b" : "a等于b")); // a小于b
    }

    public static void main(String[] args) {
        ArithmeticOperators();
        AssignOperations();
        BitOperations();
        EnhanceAssignOperations();
        ComparableOperators();
        LogicOperators();
        ThreeOperators();
    }
}
